﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using YSGetLatest.Commands;
using YSGetLatest.DataObjects;
using YSGetLatest.Properties;

namespace YSGetLatest.ViewModels
{
	/// <summary>
	/// View model for project window
	/// </summary>
	public class ProjectWindowViewModel
	{
		#region Fields

		private readonly Window window_;
		private readonly ProgressBar progressBarStatus_;
		private readonly Control statusBarItemProgressBar_;
		private readonly TextBlock textBlockFolderNameStatus_;
		private readonly TextBlock textBlockStatus_;
		private readonly Project project_;
		private readonly IYSCommand okCommand_ = new Command();
		private readonly IYSCommand deleteFolderCommand_ = new Command();
		private readonly IYSCommand addFolderCommand_ = new Command();
		private readonly IYSCommand editFolderCommand_ = new Command();
		private readonly IYSCommand cancelCommand_ = new Command();
		private readonly IYSCommand getLatestCommand_ = new Command();
		private readonly IYSCommand getLatestForOneFolderCommand_ = new Command();
		private readonly IYSCommand seeLogCommand_ = new Command();
		private readonly IYSCommand stopGetLatestCommand_ = new Command();
		private readonly EnablementManager enablementManager_;

		private Folder selectedFolder_;
		private int succeeded_;
		private int failed_;
		private bool running_;
		private bool canClose_;
		private StringBuilder outputBuffer_;
		#endregion

		#region Properties

		/// <summary>
		/// Gets the Project.
		/// </summary>
		public Project Project
		{
			get
			{
				return project_;
			}
		}

		/// <summary>
		/// Gets the save command
		/// </summary>
		public IYSCommand OKCommand
		{
			get
			{
				return okCommand_;
			}
		}

		/// <summary>
		/// Gets the delete command
		/// </summary>
		public IYSCommand DeleteFolderCommand
		{
			get
			{
				return deleteFolderCommand_;
			}
		}

		/// <summary>
		/// Gets the add command
		/// </summary>
		public IYSCommand AddFolderCommand
		{
			get
			{
				return addFolderCommand_;
			}
		}

		/// <summary>
		/// Gets the add command
		/// </summary>
		public IYSCommand EditFolderCommand
		{
			get
			{
				return editFolderCommand_;
			}
		}

		/// <summary>
		/// Gets the close command
		/// </summary>
		public IYSCommand CancelCommand
		{
			get
			{
				return cancelCommand_;
			}
		}

		/// <summary>
		/// Gets the GetLatest Command.
		/// </summary>
		public IYSCommand GetLatestCommand
		{
			get
			{
				return getLatestCommand_;
			}
		}

		/// <summary>
		/// Gets the GetLatest for one folder Command.
		/// </summary>
		public IYSCommand GetLatestForOneFolderCommand
		{
			get
			{
				return getLatestForOneFolderCommand_;
			}
		}

		/// <summary>
		/// Gets the SeeLog Command.
		/// </summary>
		public IYSCommand SeeLogCommand
		{
			get
			{
				return seeLogCommand_;
			}
		}

		/// <summary>
		/// Gets the StopGetLatest Command.
		/// </summary>
		public IYSCommand StopGetLatestCommand
		{
			get
			{
				return stopGetLatestCommand_;
			}
		}

		/// <summary>
		/// Gets the values that indicates whether the window may be closed.
		/// </summary>
		public bool CanClose
		{
			get
			{
				return canClose_;
			}
		}
		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the ProjectWindowViewModel class.
		/// </summary>
		/// <param name="window"></param>
		/// <param name="listViewFolders"></param>
		/// <param name="progressBarStatus"></param>
		/// <param name="statusBarItemProgressBar"></param>
		/// <param name="textBlockFolderNameStatus"></param>
		/// <param name="textBlockStatus"></param>
		/// <param name="project"></param>
		public ProjectWindowViewModel(Window window, ListView listViewFolders, ProgressBar progressBarStatus,
			Control statusBarItemProgressBar, TextBlock textBlockFolderNameStatus, TextBlock textBlockStatus,
		Project project)
		{
			if (window == null)
			{
				throw new ArgumentNullException("window");
			}
			if (project == null)
			{
				throw new ArgumentNullException("project");
			}
			if (progressBarStatus == null)
			{
				throw new ArgumentNullException("progressBarStatus");
			}
			if (statusBarItemProgressBar == null)
			{
				throw new ArgumentNullException("statusBarItemProgressBar");
			}
			if (textBlockFolderNameStatus == null)
			{
				throw new ArgumentNullException("textBlockFolderNameStatus");
			}
			if (textBlockStatus == null)
			{
				throw new ArgumentNullException("textBlockStatus");
			}

			window_ = window;
			project_ = project;
			progressBarStatus_ = progressBarStatus;
			statusBarItemProgressBar_ = statusBarItemProgressBar;
			textBlockFolderNameStatus_ = textBlockFolderNameStatus;
			textBlockStatus_ = textBlockStatus;
			okCommand_.OnExecute += OnOk;
			deleteFolderCommand_.OnExecute += OnDeleteFolder;
			cancelCommand_.OnExecute += OnCancel;
			addFolderCommand_.OnExecute += OnAddFolder;
			editFolderCommand_.OnExecute += OnEditFolder;
			getLatestCommand_.OnExecute += OnGetLatest;
			getLatestForOneFolderCommand_.OnExecute += OnGetLatestForOneFolder;
			seeLogCommand_.OnExecute += OnSeeLog;
			stopGetLatestCommand_.OnExecute += OnStoGetLatest;

			deleteFolderCommand_.Enable(false);
			editFolderCommand_.Enable(false);
			getLatestCommand_.Enable(Project.Folders.Count > 0);
			seeLogCommand_.Enable(false);
			stopGetLatestCommand_.Enable(false);


			enablementManager_ = new EnablementManager();
			enablementManager_.AddCommand(okCommand_,
				ExecutionState.NoRunning,
				new ListState[] { ListState.All }, null,
				new SelectionState[] { SelectionState.All }, null);
			enablementManager_.AddCommand(deleteFolderCommand_,
				ExecutionState.NoRunning,
				new ListState[] { ListState.NoEmpty }, new ListState[] { ListState.Empty },
				new SelectionState[] { SelectionState.OneItemSelected }, new SelectionState[] { SelectionState.ManyItemsSelected, SelectionState.NoOneItemSelected });
			enablementManager_.AddCommand(editFolderCommand_,
				ExecutionState.NoRunning,
				new ListState[] { ListState.NoEmpty }, new ListState[] { ListState.Empty },
				new SelectionState[] { SelectionState.OneItemSelected }, new SelectionState[] { SelectionState.ManyItemsSelected, SelectionState.NoOneItemSelected });
			enablementManager_.AddCommand(cancelCommand_,
				ExecutionState.NoRunning,
				new ListState[] { ListState.All }, null,
				new SelectionState[] { SelectionState.All }, null);
			enablementManager_.AddCommand(getLatestCommand_,
				ExecutionState.NoRunning,
				new ListState[] { ListState.NoEmpty }, new ListState[] { ListState.Empty },
				new SelectionState[] { SelectionState.All }, null);
			enablementManager_.AddCommand(getLatestForOneFolderCommand_,
				ExecutionState.NoRunning,
				new ListState[] { ListState.NoEmpty }, new ListState[] { ListState.Empty },
				new SelectionState[] { SelectionState.OneItemSelected }, new SelectionState[] { SelectionState.ManyItemsSelected, SelectionState.NoOneItemSelected });
			enablementManager_.AddCommand(seeLogCommand_,
				ExecutionState.All,
				new ListState[] { ListState.NoEmpty }, new ListState[] { ListState.Empty },
				new SelectionState[] { SelectionState.OneItemSelected }, new SelectionState[] { SelectionState.ManyItemsSelected, SelectionState.NoOneItemSelected });
			enablementManager_.AddCommand(stopGetLatestCommand_,
				ExecutionState.Running,
				new ListState[] { ListState.All }, null,
				new SelectionState[] { SelectionState.All }, null);

			enablementManager_.ListState = Project.Folders.Count == 0 ? ListState.Empty : ListState.NoEmpty;
			enablementManager_.SelectionState = SelectionState.NoOneItemSelected;
			enablementManager_.ExecutionState = ExecutionState.NoRunning;

			listViewFolders.SelectionChanged += OnFolderListBoxSelectionChanged;

			canClose_ = true;
		}
		#endregion

		#region Methods

		/// <summary>
		/// set enable or disable for all selected folders.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void EnabledChanged(object sender)
		{
			var isChecked = (sender as CheckBox).IsChecked;

			foreach (var folder in Project.Folders.Where(f => f.IsSelected))
			{
				folder.Enabled = isChecked.Value;
			}
		}

		private void OnFolderListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
		{

			var selectedItemsCount = Project.Folders.Where(f => f.IsSelected).Count();

			selectedFolder_ = (selectedItemsCount == 1) ? Project.Folders.Where(f => f.IsSelected).FirstOrDefault() : null;

			enablementManager_.ListState = Project.Folders.Count == 0 ? ListState.Empty : ListState.NoEmpty;

			enablementManager_.SelectionState =
				selectedItemsCount == 0 ?
				SelectionState.NoOneItemSelected
				: selectedItemsCount == 1 ?
					SelectionState.OneItemSelected
					: SelectionState.ManyItemsSelected;
		}

		private void OnOk()
		{
			window_.DialogResult = true;
			window_.Close();
		}

		private void OnDeleteFolder()
		{
			var folder = selectedFolder_;
			if (folder == null)
			{
				return;
			}

			if (MessageBox.Show("Are you sure delete the folder?", "Delete Folder", MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK)
			{
				Project.Folders.Remove(folder);
			}
		}

		private void OnCancel()
		{
			window_.Close();
		}

		private void OnAddFolder()
		{
			var folder = new Folder
			{
				Enabled = true
			};
			bool? rslt = (new FolderWindow(folder)
			{
				Owner = window_
			}).ShowDialog();

			if (!(rslt.HasValue && rslt.Value))
				return;

			Project.Folders.Add(folder);
			folder.IsSelected = true;
		}

		private void OnEditFolder()
		{
			var folder = selectedFolder_;
			if (folder == null)
			{
				return;
			}

			folder.BeginEdit();
			bool? rslt = (new FolderWindow(folder)
			{
				Owner = window_
			}).ShowDialog();

			if (rslt.HasValue && rslt.Value)
			{
				folder.EndEdit();
			}
			else
			{
				folder.CancelEdit();
			}
		}

		private void OnGetLatest()
		{
			enablementManager_.ExecutionState = ExecutionState.Running;

			ThreadPool.QueueUserWorkItem(DoGetLatest, null);
		}

		private void OnGetLatestForOneFolder()
		{
			enablementManager_.ExecutionState = ExecutionState.Running;
			ThreadPool.QueueUserWorkItem(DoGetLatest, selectedFolder_);
		}

		private void DoGetLatest(object data)
		{
			running_ = true;
			canClose_ = false;

			succeeded_ = 0;
			failed_ = 0;

			if (data == null)
			{
				foreach (var folder in Project.Folders)
				{
					folder.IsSelected = false;
				}

				StartProgressBar();
				foreach (var folder in Project.Folders.OrderBy(f => f.Name))
				{
					if (!running_)
					{
						break;
					}

					if (!folder.Enabled)
					{
						continue;
					}

					bool result = GetLatestForFolder(folder);

					if (result)
					{
						++succeeded_;
					}
					else
					{
						++failed_;
					}

					SetFolderFault(folder, result);
					IncreaseProgressBar();
					OutputStatus();
					folder.IsSelected = false;
				}
			}
			else
			{
				var folder = data as Folder;

				if (folder == null)
				{
					throw new ApplicationException("E1243: folder is null.");
				}

				if (folder.Enabled)
				{
					bool result = GetLatestForFolder(folder);
					SetFolderFault(folder, result);
				}
			}

			CompleteGetLatest();
		}

		private bool GetLatestForFolder(Folder folder)
		{
			folder.IsSelected = true;
			SetFolderNameInStatusBar(folder.Name);
			outputBuffer_ = new StringBuilder();

			bool result;
			try
			{
				var logFileName = GetLogFileName(folder.Name);
				var tfPath = Path.Combine(Settings.Default.TFLocation, "tf.exe");
				var arguments = string.Format("get {0}/{1}/{2} /overwrite /recursive",
					Project.SourceLocation, Project.Branch, folder.Name);

				using (var process = new Process
				{
					StartInfo = new ProcessStartInfo
					{
						FileName = tfPath,
						Arguments = arguments,
						UseShellExecute = false,
						RedirectStandardOutput = true,
						RedirectStandardError = true,
						WindowStyle = ProcessWindowStyle.Hidden,
						CreateNoWindow = true
					}
				})
				{
					try
					{
						process.OutputDataReceived += OnOutputDataReceived;
						process.ErrorDataReceived += OnErrorDataReceived;

						if (process.Start())
						{
							// Start the asynchronous read of the output and error streams.
							process.BeginOutputReadLine();
							process.BeginErrorReadLine();

							process.WaitForExit();
							result = (process.ExitCode == 0);

							using (var output = new StreamWriter(logFileName))
							{
								output.WriteLine("Folder: {0}, {1}", folder.Name, DateTime.Now.ToString());
								output.WriteLine("tf {0}", arguments);
								output.WriteLine("Exit Code is {0}", process.ExitCode);
								output.Write(outputBuffer_.ToString());
								output.Close();
							}
						}
						else
						{
							result = false;
						}
					}
					catch (Win32Exception ex)
					{
#if DEBUG
						ShowException(ex.ToString());
#else
						ShowException(ex.Message);
#endif
						result = false;
					}
				}
			}
			catch (Exception ex)
			{
#if DEBUG
				ShowException(ex.ToString());
#else
				ShowException(ex.Message);
#endif
				result = false;
			}

			return result;
		}

		private void StartProgressBar()
		{
			window_.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
			{
				statusBarItemProgressBar_.Visibility = Visibility.Visible;
				progressBarStatus_.Maximum = Project.Folders.Count;
				progressBarStatus_.Minimum = 0;
				progressBarStatus_.Value = 0;
			}
			));
		}

		private void SetFolderNameInStatusBar(string folderName)
		{
			window_.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
			{
				textBlockFolderNameStatus_.Text = folderName;
			}));
		}

		private void IncreaseProgressBar()
		{
			window_.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
			{
				progressBarStatus_.Value += 1;
			}
			));
		}

		private void CompleteGetLatest()
		{
			window_.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
			{
				enablementManager_.ExecutionState = ExecutionState.NoRunning;

				canClose_ = true;

				statusBarItemProgressBar_.Visibility = Visibility.Hidden;
			}
			));
		}

		private void SetFolderFault(Folder folder, bool result)
		{
			window_.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
			{
				folder.IsFailed = !result;
			}
			));
		}

		private void OutputStatus()
		{
			window_.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
			{
				textBlockStatus_.Text = string.Format("{0} succeeded, {1} failed.", succeeded_, failed_);
			}
			));
		}

		private void OnSeeLog()
		{
			if (selectedFolder_ == null)
			{
				return;
			}

			var logFileName = GetLogFileName(selectedFolder_.Name);

			using (var p = new Process
			{
				StartInfo = new ProcessStartInfo
				{
					FileName = logFileName,
					WindowStyle = ProcessWindowStyle.Normal
				}

			})
			{
				p.Start();
			}
		}

		private void OnStoGetLatest()
		{
			running_ = false;
		}

		private string GetLogFileName(string folderName)
		{
			var tempFolder = Path.GetTempPath();

			var clearFolderName = folderName.Replace('/', '_');
			clearFolderName = clearFolderName.Replace('\\', '_');

			var logFileName = Path.Combine(tempFolder, string.Format("YSGetLatest_{0}.log", clearFolderName));

			return logFileName;
		}

		private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
		{
			lock (outputBuffer_)
			{
				outputBuffer_.AppendLine(e.Data);
			}
		}

		private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
		{
			lock (outputBuffer_)
			{
				outputBuffer_.AppendLine(e.Data);
			}
		}

		private void ShowException(string text)
		{
			var thread = new Thread(() =>
			{
				var errorWindow = new ErrorWindow(text);
				errorWindow.ShowDialog();
			});
			thread.SetApartmentState(ApartmentState.STA);
			thread.Start();
		}
		#endregion
	}
}

